1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 
12 /**
13 *   Asset representation of a texture
14 */
15 module hip.assets.texture;
16 import hip.asset;
17 import hip.error.handler;
18 import hip.hiprenderer.renderer;
19 import hip.math.rect;
20 import hip.assets.image;
21 public import hip.util.data_structures:Array2D;
22 public import hip.api.renderer.texture;
23 
24 
25 import renderer = hip.hiprenderer.texture;
26 import hip.util.reflection;
27 
28 class HipTexture : HipAsset, IHipTexture
29 {
30     mixin(ForwardInterface!("textureImpl", IHipTexture));
31     
32     IImage img;
33     int width,height;
34     public IHipTexture textureImpl;
35     private bool successfullyLoaded;
36     public bool hasSuccessfullyLoaded(){return successfullyLoaded;}
37     
38     public static HipTexture getPixelTexture()
39     {
40         __gshared HipTexture pixelTexture;
41         if(pixelTexture is null)
42         {
43             pixelTexture = new HipTexture();
44             pixelTexture.img = cast(IImage)Image.getPixelImage; //Cast the immutable away, promise it is immutable
45             pixelTexture.textureImpl.load(pixelTexture.img);
46         }
47         return pixelTexture;
48     }
49 
50     IHipTexture getBackendHandle(){return textureImpl.getBackendHandle();}
51     /**
52     *   Initializes with the current renderer type
53     */
54     protected this()
55     {
56         super("Texture");
57         _typeID = assetTypeID!HipTexture;
58         textureImpl = HipRenderer.getTextureImplementation();
59     }
60 
61 
62     this(in IImage image)
63     {
64         this();
65         if(image !is null)
66             load(image);
67     }
68 
69     import hip.util.string;
70     String toHipString()
71     {
72         return String("TEX[", width, "x",height,"] ", img.getSizeBytes, " bytes");
73     }
74     alias load = IHipTexture.load;
75 
76 
77     protected bool loadImpl(in IImage img)
78     {
79         this.img = cast(IImage)img;
80         successfullyLoaded = textureImpl.load(img);
81         width = textureImpl.getWidth;
82         height = textureImpl.getHeight;
83         return successfullyLoaded;
84     }
85     
86     override void onFinishLoading(){}
87     override void onDispose(){}
88     
89     bool isReady(){return textureImpl !is null;}
90     int getWidth(){return width;}
91     int getHeight(){return height;}
92     
93 }
94 
95 
96 
97 class HipTextureRegion : HipAsset, IHipTextureRegion
98 {
99     public static const float[8] defaultVertices = [0,0, 1,0, 1,1, 0,1];
100     IHipTexture texture;
101     public float u1, v1, u2, v2;
102     protected float[8] vertices;
103     protected float[8] verticesTransformed;
104     private bool flippedX, flippedY;
105     int regionWidth, regionHeight;
106 
107     bool hasSuccessfullyLoaded(){return texture && texture.hasSuccessfullyLoaded;}
108 
109     protected this()
110     {
111         super("TextureRegion");
112         _typeID = assetTypeID!HipTextureRegion;
113     }
114 
115     this(IHipTexture texture, float u1 = 0, float v1 = 0, float u2 = 1, float v2 = 1)
116     {
117         this();
118         this.texture = texture;
119         setRegion(u1,v1,u2,v2);
120     }
121     this(IHipTexture texture, uint u1, uint v1, uint u2, uint v2)
122     {
123         this();
124         this.texture = texture;
125         setRegion(texture.getWidth, texture.getHeight, u1,  v1, u2, v2);
126     }
127 
128     void setTexture(IHipTexture texture){this.texture = texture;}
129     const(IHipTexture) getTexture() const {return cast(const)texture;}
130     IHipTexture getTexture() {return texture;}
131     int getWidth() const {return regionWidth;}
132     int getHeight() const {return regionHeight;}
133     TextureCoordinatesQuad getRegion() const
134     {
135         return TextureCoordinatesQuad(u1, v1, u2, v2);
136     }
137 
138     /**
139     * By passing the width and height values, you'll be able to crop useless frames
140     * Default spritesheet method that makes a spritesheet from the entire texture
141     */
142     public static Array2D!IHipTextureRegion cropSpritesheet(
143         IHipTexture t,
144         uint frameWidth, uint frameHeight,
145         uint width = 0, uint height = 0,
146         uint offsetX = 0, uint offsetY = 0,
147         uint offsetXPerFrame = 0, uint offsetYPerFrame = 0)
148     {
149         if(width == 0) width = t.getWidth;
150         if(height == 0) height = t.getHeight;
151 
152         uint lengthW = width/(frameWidth+offsetXPerFrame);
153         uint lengthH = height/(frameHeight+offsetYPerFrame);
154 
155         Array2D!IHipTextureRegion ret = Array2D!IHipTextureRegion(lengthH, lengthW);
156 
157         for(int i = 0, fh = 0; fh < height; i++, fh+= frameHeight+offsetXPerFrame)
158             for(int j = 0, fw = 0; fw < width; j++, fw+= frameWidth+offsetYPerFrame)
159                 ret[i,j] = new HipTextureRegion(t, offsetX+fw , offsetY+fh, offsetX+fw+frameWidth, offsetY+fh+frameHeight);
160 
161         return ret;
162     }
163     public static Array2D!IHipTextureRegion cropSpritesheetRowsAndColumns(IHipTexture t, uint rows, uint columns)
164     {
165         uint frameWidth = t.getWidth() / columns;
166         uint frameHeight = t.getHeight() / rows;
167         return cropSpritesheet(t,frameWidth,frameHeight, t.getWidth, t.getHeight, 0, 0, 0, 0);
168     }
169     
170 
171     alias setRegion = IHipTextureRegion.setRegion;
172     /**
173     *   Defines a region for the texture in the following order:
174     *   Top-left
175     *   Top-Right
176     *   Bot-Right
177     *   Bot-Left
178     */
179     public void setRegion(float u1, float v1, float u2, float v2)
180     {
181         this.u1 = u1;
182         this.u2 = u2;
183         this.v1 = v1;
184         this.v2 = v2;
185         //Check for round
186         float regWidth =  (u2 - u1) * texture.getWidth;
187         float regHeight = (v2 - v1) * texture.getHeight;
188         regionWidth =  cast(uint)(regWidth + 0.5) > cast(uint)regWidth ? cast(uint)(regWidth+0.5) : cast(uint)regWidth;
189         regionHeight = cast(uint)(regHeight + 0.5) > cast(uint)regHeight ? cast(uint)(regHeight+0.5) : cast(uint)regHeight;
190 
191         //Top left
192         vertices[0] = u1;
193         vertices[1] = v1;
194 
195         //Top right
196         vertices[2] = u2;
197         vertices[3] = v1;
198         
199         //Bot right
200         vertices[4] = u2;
201         vertices[5] = v2;
202 
203         //Bot left
204         vertices[6] = u1;
205         vertices[7] = v2;
206 
207         if(flippedX)
208         {
209             flippedX = false;
210             setFlippedX(true);
211         }
212         if(flippedY)
213         {
214             flippedY = false;
215             setFlippedY(true);
216         }
217     }
218 
219     HipTextureRegion clone()
220     {
221         return new HipTextureRegion(texture, u1, v1, u2, v2);
222     }
223     void setFlippedX(bool flip)
224     {
225         if(flip != flippedX)
226         {
227             flippedX = flip;
228             vertices[0] = flip ? u2 : u1;
229             vertices[2] = flip ? u1 : u2;
230             vertices[4] = flip ? u1 : u2;
231             vertices[6] = flip ? u2 : u1;
232         }
233     }
234     void setFlippedY(bool flip)
235     {
236         if(flip != flippedY)
237         {
238             flippedY = flip;
239             vertices[1] = flip ? v2 : v1;
240             vertices[3] = flip ? v2 : v1;
241             vertices[5] = flip ? v1 : v2;
242             vertices[7] = flip ? v1 : v2;
243         }
244     }
245     bool isFlippedX(){return flippedX;}
246     bool isFlippedY(){return flippedY;}
247 
248     ref float[8] getVertices()
249     {
250         return vertices;
251     }
252     override void onFinishLoading(){}
253     override void onDispose(){}
254     bool isReady(){return texture !is null;}
255     
256 }